package fem2.jlinalg;

import inf.jlinalg.IMatrix;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import fem2.MeshPart;
import fem2.Model;

/**
 * Symbolic table store entry's information of compressed matrix
 * 
 * @author hbui
 * 
 */
public class SymbolicTable {

	private int nRows;
	private int nCols;
	private Set<Integer>[] rowInd;
	private boolean symmetric = false;

	@SuppressWarnings("unchecked")
	public SymbolicTable(int nRows, int nCols) {
		this.nRows = nRows;
		this.nCols = nCols;
		this.rowInd = new HashSet[nCols];
		for (int i = 0; i < nCols; i++) {
			rowInd[i] = new HashSet<Integer>();
		}
	}

	/**
	 * mark the entry at row i, col j of symbolic table
	 * 
	 * @param i
	 * @param j
	 */
	public void set(int i, int j) {
		if (!this.symmetric) {
			rowInd[j].add(i);
		} else {
			if (i <= j) {
				rowInd[j].add(i);
			}
		}
	}

	/**
	 * create the symbolic table from model
	 * 
	 * @param model
	 */
	public void initializeFrom(Model model) {
		int ne = model.countElements();
		for (int i = 0; i < ne; i++) {
			MeshPart mp = model.getElement(i).getMeshPart();
			if (!mp.getElement().isInactive()) {
				int[] dofs = mp.getDofNumbers();
				for (int j = 0; j < dofs.length; j++) {
					for (int k = 0; k < dofs.length; k++) {
						if (dofs[j] != -1 && dofs[k] != -1) {
							this.set(dofs[j], dofs[k]);
						}
					}
				}
			}
		}
	}

	/**
	 * create the empty compressed sparse column matrix. By default, the pattern
	 * of CSC matrix is created (column pointer & row index). The values is
	 * zeros
	 * 
	 * @return the csc matrix
	 */
	public IMatrix createCSCMatrix() {
		int[] colptr = new int[nCols + 1];
		int nEntries = this.getNumbeOfEntries();
		int[] rowind = new int[nEntries];
		int cnt = 0;
		for (int i = 0; i < nCols; i++) {
			ArrayList<Integer> L = new ArrayList<Integer>(rowInd[i]);
			Collections.sort(L);
			for (int j = 0; j < L.size(); j++) {
				rowind[cnt++] = L.get(j);
			}
			colptr[i + 1] = cnt;
		}
		CSCMatrix M = new CSCMatrix(nRows, colptr, rowind);
		return M;
	}

	/**
	 * create the empty coordinate list matrix. By default, the pattern of COO
	 * matrix is created (iRow & jCol). The values is zeros
	 * 
	 * @return the coo matrix
	 */
	public IMatrix createCOOMatrix() {
		int nEntries = this.getNumbeOfEntries();
		int[] iRow = new int[nEntries];
		int[] jCol = new int[nEntries];
		int cnt = 0;
		for (int i = 0; i < nCols; i++) {
			ArrayList<Integer> L = new ArrayList<Integer>(rowInd[i]);
			Collections.sort(L);
			for (int j = 0; j < L.size(); j++) {
				iRow[cnt] = L.get(j);
				jCol[cnt] = i;
				cnt++;
			}
		}
		IMatrix M;
		if (!this.symmetric) {
			M = new COOMatrix(nRows, nCols, iRow, jCol);
		} else {
			M = new SymmetricCooMatrix(nRows, nCols, iRow, jCol);
		}
		return M;
	}

	/**
	 * get number of entries of the matrix
	 * 
	 * @return
	 */
	public int getNumbeOfEntries() {
		int nEntries = 0;
		for (int i = 0; i < nCols; i++) {
			nEntries += rowInd[i].size();
		}
		return nEntries;
	}

	/**
	 * set it to true if you know the symmetric property of matrix. It it's true
	 * then Symbolic table will return the SymmetricCooMatrix. By default, it's
	 * false
	 * 
	 * @param symmetric
	 */
	public void setSymmetric(boolean symmetric) {
		this.symmetric = symmetric;
	}

	/**
	 * 
	 * @return true if symmetric property is set
	 */
	public boolean isSymmetric() {
		return this.symmetric;
	}

	/**
	 * get number of entries in each row as array
	 * 
	 * @return
	 */
	public int[] getNumbeOfRowEntriesAsArray() {
		int[] nnz = new int[nRows];
		for (int i = 0; i < nCols; i++) {
			ArrayList<Integer> L = new ArrayList<Integer>(rowInd[i]);
			for (int j = 0; j < L.size(); j++) {
				nnz[L.get(j)]++;
			}
		}
		return nnz;
	}
}
